use super::MessageError;
use crate::runtime::{Class, Object, Sel};
use crate::{Encode, EncodeArguments};

pub fn verify_message_signature<A, R>(cls: &Class, sel: Sel) -> Result<(), MessageError>
where
    A: EncodeArguments,
    R: Encode,
{
    let method = match cls.instance_method(sel) {
        Some(method) => method,
        None => return Err(MessageError(format!("Method {:?} not found on class {:?}", sel, cls))),
    };

    let ret = R::encode();
    let expected_ret = method.return_type();
    if ret != expected_ret {
        return Err(MessageError(format!(
            "Return type code {:?} does not match expected {:?} for method {:?}",
            ret,
            expected_ret,
            method.name()
        )));
    }

    let self_and_cmd = [<*mut Object>::encode(), Sel::encode()];
    let args = A::encodings();
    let args = args.as_ref();

    let count = self_and_cmd.len() + args.len();
    let expected_count = method.arguments_count();
    if count != expected_count {
        return Err(MessageError(format!(
            "Method {:?} accepts {} arguments, but {} were given",
            method.name(),
            expected_count,
            count
        )));
    }

    for (i, arg) in self_and_cmd.iter().chain(args).enumerate() {
        let expected = method.argument_type(i).unwrap();
        if *arg != expected {
            return Err(MessageError(format!(
                "Method {:?} expected argument at index {} with type code {:?} but was given {:?}",
                method.name(),
                i,
                expected,
                arg
            )));
        }
    }

    Ok(())
}
